package com.luyang.framework.protocol.interceptor;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.BetweenFormatter;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.SystemClock;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.zaxxer.hikari.pool.HikariProxyPreparedStatement;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Proxy;
import java.sql.Statement;
import java.util.Comparator;
import java.util.List;
import java.util.Set;

/**
 * SQL 日志
 *
 * @author lu_yang
 */
@Intercepts({
    @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
    @Signature(type = StatementHandler.class, method = "update", args = Statement.class),
    @Signature(type = StatementHandler.class, method = "batch", args = Statement.class)
})
public class SqlLogInterceptor implements Interceptor {

    private static final Logger logger = LoggerFactory.getLogger(SqlLogInterceptor.class);

    public @Override Object intercept(Invocation invocation) throws Throwable {
        HikariProxyPreparedStatement statement;
        // 获取方法参数
        Object firstArg = invocation.getArgs()[0];
        if (Proxy.isProxyClass(firstArg.getClass())) {
            statement = (HikariProxyPreparedStatement) SystemMetaObject.forObject(firstArg).getValue("h.statement");
        } else {
            statement = (HikariProxyPreparedStatement) firstArg;
        }

        String originalSql = statement.toString().replaceAll("[\\s]+", StringPool.SPACE);
        int index = this.indexOfSqlStart(originalSql);
        if (index > 0) {
            originalSql = originalSql.substring(index);
        }

        long startTime = SystemClock.now();
        try {
            return invocation.proceed();
        } finally {

            long timing = SystemClock.now() - startTime;

            // SQL 打印执行结果
            Object target = PluginUtils.realTarget(invocation.getTarget());
            MetaObject metaObject = SystemMetaObject.forObject(target);
            MappedStatement ms = (MappedStatement) metaObject.getValue("delegate.mappedStatement");

            // 打印 sql
            String sqlLogger = """
                ==============  SQL Execute Start  ==============
                Execute ID  ：{}
                Execute SQL ：{}
                Execute Time：{}
                ==============  SQL  Execute End   ==============
                """;

            String timeCost = DateUtil.formatBetween(timing, BetweenFormatter.Level.MILLISECOND);
            logger.info(sqlLogger, ms.getId(), originalSql, timeCost);
        }
    }

    public @Override Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    /**
     * 获取 SQL 开头语句
     *
     * @param sql SQL
     * @return int
     * @author lu_yang
     */
    private int indexOfSqlStart(String sql) {
        String upperCaseSql = sql.toUpperCase();
        Set<Integer> set = Sets.newHashSet();
        set.add(upperCaseSql.indexOf("SELECT "));
        set.add(upperCaseSql.indexOf("UPDATE "));
        set.add(upperCaseSql.indexOf("INSERT "));
        set.add(upperCaseSql.indexOf("DELETE "));
        set.remove(-1);
        if (CollUtil.isEmpty(set)) {
            return -1;
        }
        List<Integer> list = Lists.newArrayList(set);
        list.sort(Comparator.naturalOrder());
        return list.get(0);
    }
}
